﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace TD.Interface.Controls
{
    internal abstract class ButtonBase : DrawableGameComponent
    {
        private readonly Color FONT_COLOR_LINK = Color.FromNonPremultiplied(0xD0, 0xD0, 0xFF, 0xFF);
        private readonly Color FONT_COLOR_HOVER = Color.FromNonPremultiplied(0xFF, 0xFF, 0xFF, 0xFF);

        /// <summary>
        /// Indicates whether or not the alpha has to be count as the button or not.
        /// True means the alpha is not part of the button.
        /// </summary>
        public bool RenderAlpha { get; set; }
        public bool HasFocus { get; set; }
        public Point Point { get; set; }

        private SpriteFont font;
        private SpriteFont subFont;

        private SpriteBatch spriteBatch;
        private Texture2D texture;
        private string assetName;

        protected string text;
        protected string subText;
        protected Vector2 textCoord;
        protected Vector2? subTextCoord;

        public ButtonBase(Game1 game, string assetName, Point point, string text, Vector2 textCoord, string subText = "", Vector2? subTextCoord = null) :
            base(game)
        {
            this.assetName = assetName;
            this.Point = point;
            this.text = text;
            this.textCoord = textCoord;
            this.subText = subText;
            this.subTextCoord = subTextCoord;

            // By default the alpha is not counted as the button.
            RenderAlpha = true;

            // Controls are just behind the mouse.
            base.DrawOrder = 0x08200000;
        }

        public override void Initialize()
        {
            HasFocus = false;
            
            base.Initialize();
        }

        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // Load the fonts.
            font = base.Game.Content.Load<SpriteFont>("interface/MenuFont");
            subFont = base.Game.Content.Load<SpriteFont>("interface/MenuSubFont");

            // Load the button's texture.
            ((Game1)Game).TextureManager.LoadAsset(assetName);
            texture = ((Game1)Game).TextureManager.UseAsset(assetName);
            
            base.LoadContent();
        }

        protected override void UnloadContent()
        {
            if (texture != null)
            {
                texture.Dispose();
                texture = null;
            }
            
            base.UnloadContent();
        }

        public override void Update(GameTime gameTime)
        {
            HasFocus = IsHover(this);

            if (HasFocus)
            {
                if (((Game1)Game).InputManager.IsMouseClicked())
                {
                    Click();
                }
            }

            Modifiers(gameTime);
            base.Update(gameTime);
        }
        protected abstract void Modifiers(GameTime gameTime);

        public event OnClick MouseClick;
        public delegate void OnClick(object sender);
        protected virtual void Click()
        {
            if (MouseClick != null)
            {
                MouseClick(this);
                PlaySound();
            }
        }

        protected virtual void PlaySound()
        {
            SoundManager manager = ((Game1)Game).SoundManager;
            manager.LoadAsset("button_focus");
            manager.Play("button_focus");
        }

        public override void Draw(GameTime gameTime)
        {
            int x = 0x00;
            if (HasFocus)
                x = (texture.Width >> 1);

            // Texture
            spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied);
            spriteBatch.Draw(
                texture,
                new Rectangle(Point.X, Point.Y, texture.Width >> 1, texture.Height),
                new Rectangle(x, 0, texture.Width >> 1, texture.Height),
                Color.White);
            spriteBatch.End();

            // The text color is determined by whether the button has focus or not.
            Color color = (HasFocus ? FONT_COLOR_HOVER : FONT_COLOR_LINK);

            // Text
            spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);
            spriteBatch.DrawString(font, text, new Vector2(textCoord.X + Point.X, textCoord.Y + Point.Y), color);
            if (subTextCoord.HasValue)
            {
                spriteBatch.DrawString(subFont, subText, new Vector2(subTextCoord.Value.X + Point.X, subTextCoord.Value.Y + Point.Y), color);
            }
            spriteBatch.End();

            base.Draw(gameTime);
        }

        private static bool IsHover(ButtonBase button)
        {
            Point cursor = ((Game1)button.Game).InputManager.GetMouseLocation();

            // The width and height of both pictures.
            int width = (button.texture.Width >> 1);
            int height = button.texture.Height;

            // X-axis matches.
            if (cursor.X >= button.Point.X && cursor.X < (button.Point.X + width))
            {
                // Y-axis matches.
                if (cursor.Y >= button.Point.Y && cursor.Y < (button.Point.Y + height))
                {
                    // When we have to ignore alpha it is true already.
                    if (!button.RenderAlpha)
                        return true;

                    // The X- and Y-location on the button in pixels.
                    int x = (cursor.X - button.Point.X);
                    int y = (cursor.Y - button.Point.Y);

                    int img1 = ((y * (width * 2)) + x); // The location on the left image.
                    int img2 = (img1 + width); // The location on the right image.
                    
                    int a = 0xFF;

                    Color[] data = new Color[button.texture.Width * button.texture.Height];
                    button.texture.GetData<Color>(data);

                    if (button.HasFocus)
                    {
                        a = (data[img1].A | data[img2].A);
                    }
                    else
                    {
                        a = data[img1].A;
                    }

                    return ((a & 0x80) > 0);
                }
            }

            return false;
        }
    }
}
